#pragma once

#include "Data.hpp"
#include "httplib.h"

extern cloud::DataManager *data_;

namespace cloud
{
    // 服务端处理相关业务请求
    class Service
    {
    public:
        Service()
        {
            Config *config = Config::GetInstance();
            server_port_ = config->GetServerPort();
            server_ip_ = config->GetServerIp();
            download_prefix_ = config->GetDownloadPrefix();
        }

        bool RunModule()
        {
            // 上传请求
            server_.Post("/upload", Upload);
            // 备份文件信息展示请求
            server_.Get("/listshow", ListShow);
            server_.Get("/", ListShow);
            // 下载请求
            std::string download_url = download_prefix_ + "(.*)";
            server_.Get(download_url, Download);
            // 服务器开始监听
            server_.listen(server_ip_.c_str(), server_port_);
            return true;
        }

    private:
        static std::string TimetoStr(time_t t)
        {
            std::string tmp = std::ctime(&t);
            return tmp;
        }

        static std::string GetETag(const BackupInfo &info)
        {
            // Etag : filename-file_size-ModifyTime 文件的唯一标识
            FileUtil fu(info.real_path);
            std::string etag = fu.FileName();
            etag += "-";
            etag += std::to_string(info.file_size);
            etag += "-";
            etag += std::to_string(info.Modifytime);
            return etag;
        }

        // 上传请求
        static void Upload(const httplib::Request &req, httplib::Response &res)
        {
            // 1. 判断请求中，有没有上传的文件区域
            auto ret = req.has_file("file");
            if (!ret)
            {
                res.status = 400;
                return;
            }
            // 2. 如果有，就获取文件的相关数据
            const auto &file = req.get_file_value("file");
            // 3. 在服务端获取备份文件实际存储路径，然后在路径下创建相应的文件
            std::string back_dir = Config::GetInstance()->GetBackDir();
            // 3.1 文件实际存储路径
            std::string realpath = back_dir + FileUtil(file.filename).FileName();
            // 4. 将数据写入文件中
            FileUtil fu(realpath);
            fu.SetContent(file.content);
            // 5. 创建对应的备份文件的管理信息，并保存起来
            BackupInfo info;
            info.NewBackupInfo(realpath);
            data_->Insert(info);
            return;
        }

        // 备份文件信息展示请求
        static void ListShow(const httplib::Request &req, httplib::Response &res)
        {
            // 1. 获取所有文件的备份信息
            std::vector<BackupInfo> arry;
            data_->GetAll(&arry);
            // 2. 根据所有备份信息，组织 html 文件数据
            std::stringstream ss;
            ss << "<html><head><title>Download</title></head>";
            ss << "<body><h1>Download</h1><table>";
            for (auto &file : arry)
            {
                ss << "<tr>";
                std::string filename = FileUtil(file.real_path).FileName();
                ss << "<td><a href='" << file.url_path << "'>" << filename << "</a></td>";
                ss << "<td align='right'>" << TimetoStr(file.Modifytime) << "</td>";
                ss << "<td align='right'>" << (file.file_size / 1024) << "k</td>";
                ss << "</tr>";
            }
            ss << "</table></body></html>";
            res.body = ss.str();
            res.set_header("Content-Type", "text/html");
            res.status = 200;
            return;
        }

        // 下载请求
        static void Download(const httplib::Request &req, httplib::Response &res)
        {
            // 1. 根据 url 获取备份文件信息
            BackupInfo info;
            data_->GetOneByURL(req.path, &info);

            // 2.文件如果被压缩，就先解压缩文件到备份目录下，然后删除压缩包并修改备份信息
            if (info.pack_flag)
            {
                FileUtil tmp(info.pack_path);
                tmp.UnCompress(info.real_path);
                tmp.Remove();
                info.pack_flag = false;
                data_->Update(info);
            }

            // 4. 如果请求req中有 If-Range 字段，说明这是一个断点续传请求
            // 如果请求req中没有 If-Range 字段，说明这是一个普通的下载请求，或者
            // 如果有这个字段，但是它的值与当前文件的 ETage 不一样，说明文件修改过了，
            // 就必须返回全部的数据
            bool retrans = false;
            std::string old_etag;
            if (req.has_header("If-Range"))
            {
                old_etag = req.get_header_value("If-Range");
                // 有If-Range字段且，这个字段的值与请求文件的最新etag一致则符合断点续传
                if (old_etag == GetETag(info))
                {
                    retrans = true;
                }
            }

            // 4. 读取文件数据，将内容写入 res.body 响应中
            FileUtil fu(info.real_path);
            if (retrans == false)
            {
                fu.GetContent(&res.body);
                // 5. 设置响应头部字段：有 Etag, Accept-Ranges(bytes)
                res.set_header("Accept-Ranges", "bytes");
                res.set_header("ETag", GetETag(info));
                res.set_header("Content-Type", "application/octet-stream");
                res.status = 200;
            }
            else
            {
                // httplib内部实现了对于区间请求也就是断点续传请求的处理
                // 只需要我们用户将文件所有数据读取到rsp.body中，它内部会自动根据请求
                // 区间，从body中取出指定区间数据进行响应
                //  std::string  range = req.get_header_val("Range"); bytes=start-end
                fu.GetContent(&res.body);
                res.set_header("Accept-Ranges", "bytes");
                res.set_header("ETag", GetETag(info));
                res.set_header("Content-Type", "application/octet-stream");
                // rsp.set_header("Content-Range", "bytes start-end/fsize");
                res.status = 206; // 区间请求响应的是206*****
            }

            return;
        }

    private:
        int server_port_;
        std::string server_ip_;
        std::string download_prefix_;
        httplib::Server server_;
    };
}
